package com.xzk.ExpressInnWeb.util;

import com.xzk.ExpressInnWeb.bean.Express;

import java.sql.*;
import java.sql.Date;
import java.util.*;

/**
 * 数据库查询工具类
 */
public class QueryUtil {

    /**
     * 用于DQL查询，即有返回结果集ResultSet的Select查询
     *
     * @param sql 预编译的SQL语句
     * @param params 需要填充的SQL语句参数，若无参数，传入null即可
     * @return ResultSet结果集对象，该对象必须在调用该方法的方法中释放资源(close)
     */
    public static ResultSet query(String sql, List<Map<ParamType,Object>> params, PreparedStatement state, Connection conn) {
        state = getState(sql, params,conn,state);
        try {
            return state.executeQuery();
        } catch (SQLException|NullPointerException throwables) {
            throwables.printStackTrace();
        }
        return null;
    }

    /**
     * 用于DML查询，即Update、Insert、Delete，使用executeUpdate()方法，
     * 查询返回的结果大于0即为操作成功，返回true，否则返回false；
     * DML查询不返回结果集，资源均已释放。
     *
     * @param sql 预编译的SQL语句
     * @param params 需要填充的SQL语句参数，若无参数，传入null即可
     * @return 操作成功返回true, 操作失败返回false
     * @throws SQLException 异常内包含数据duplicate，需要上层方法进行处理，故抛出
     */
    public static boolean queryDML(String sql, List<Map<ParamType,Object>> params, PreparedStatement state, Connection conn) throws SQLException {
        int rs = 0;
        state = getState(sql, params, conn, state);
        try {
            rs = state.executeUpdate();
        } catch (SQLException throwables) {
            throw throwables;
        } finally {
            DruidUtil.close(conn, state);
        }
        return rs > 0;
    }

    /**
     * 私有工具方法，用于获得PreparedStatement对象并填充参数：
     *
     * 若需要填充参数，因参数的数量和类型等均不确定，使用了以下的数据结构：<br/>
     * {@code List<Map<Enum,Object>>}，并配合枚举类ParamType和switch，
     * 参数本身放入Map的Object中(使用时向下转型即可);
     *
     * 若不需要填充参数，传入null即可。
     *
     * @param sql 预编译的SQL语句
     * @param params 需要填充的SQL语句参数：
     *               数据结构为{@code List<Map<Enum,Object>>}，
     *               即元素为Map的List, Map中的Key为Enum类ParamType的枚举值(参数的数据类型)，
     *               Map中的Value为Object，即参数本身，每个Map内仅可有一对Key-Value；
     *               若无需要填充的参数，传入null即可。
     * @return PreparedStatement对象，该对象必须在调用该方法的方法中释放资源(close)。
     */
    private static PreparedStatement getState(String sql, List<Map<ParamType,Object>> params, Connection conn, PreparedStatement state){
        conn = DruidUtil.getConnection();
        try {
            state = conn.prepareStatement(sql);
            if (params != null && params.size() != 0) {
                for (int i = 0; i < params.size(); i++) {
                    Set<ParamType> keys = params.get(i).keySet();
                    ParamType key = null;
                    Object obj = null;
                    for (ParamType type: keys) {
                        key = type;
                        obj = params.get(i).get(type);
                    }
                    switch (key) {
                        case INT:
                            state.setInt(i+1, (Integer) obj);
                            break;
                        case TIMESTAMP:
                            state.setTimestamp(i+1, (Timestamp) obj);
                            break;
                        case STRING:
                            state.setString(i+1, (String) obj);
                            break;
                        case BOOLEAN:
                            state.setBoolean(i+1, (Boolean) obj);
                            break;
                        case DOUBLE:
                            state.setDouble(i+1, (Double) obj);
                            break;
                        case FLOAT:
                            state.setFloat(i+1, (Float) obj);
                            break;
                        case NULL:
                            state.setObject(i+1, obj);
                            break;
                        default:
                            System.err.println("无法处理的数据类型！");
                            return null;
                    }
                }
            }
        } catch (SQLException|NullPointerException throwables) {
            throwables.printStackTrace();
        }
        return state;
    }

    /**
     * 用于获得存储参数数据类型Enum和参数本身的Map
     *
     * @param type 参数数据类型对应的Enum值
     * @param obj 参数本身
     * @return Map对象
     */
    public static Map<ParamType,Object> getMap(ParamType type, Object obj) {
        Map<ParamType,Object> map = new HashMap<>();
        map.put(type,obj);
        return map;
    }

    /**
     * 用于获得存储参数Map的List
     *
     * @param maps 存储参数数据类型Enum和参数本身的Map数组，为可变参数(即不定多个map)
     * @return List对象
     */
    @SafeVarargs
    public static List<Map<ParamType,Object>> getList(Map<ParamType,Object> ... maps) {
        List<Map<ParamType,Object>> list = new ArrayList<>();
        if (maps != null && maps.length > 0) {
            list.addAll(Arrays.asList(maps));
        }
        return list;
    }

    /**
     * 用于获得由数据库返回的快递信息组建的快递Express对象
     *
     * @param rs DQL查询结果集
     * @return Express对象，若出现任何异常，则返回null
     */
    public static Express buildExpress(ResultSet rs) {
        try {
            int id = rs.getInt("id");
            String ex_num = rs.getString("ex_num");
            String username = rs.getString("username");
            String userphone = rs.getString("userphone");
            String company = rs.getString("company");
            String code = rs.getString("code");
            Timestamp intime = rs.getTimestamp("intime");
            Timestamp outtime = rs.getTimestamp("outtime");
            int status = rs.getInt("status");
            String staffphone = rs.getString("staffphone");
            return new Express(id, ex_num, username, userphone, company, code, intime, outtime, status, staffphone);
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        }
        return null;
    }

    public static String getType(Object o) {
        return o.getClass().toString();
    }

    public static <T> ResultSet modifyResultSet(String sql, T param, Connection conn, PreparedStatement state, ResultSet rs){
        ResultSet rsOut = rs;
        if (getType(param).endsWith("String")){
            rsOut = QueryUtil.query(
                    sql,
                    QueryUtil.getList(QueryUtil.getMap(ParamType.STRING,param)),
                    state, conn);
        } else if (getType(param).endsWith("Integer")) {
            rsOut = QueryUtil.query(
                    sql,
                    QueryUtil.getList(QueryUtil.getMap(ParamType.INT,param)),
                    state, conn);
        } else {
            System.err.println("无法处理的数据类型！");
        }
        return rsOut;
    }

    public static Map<String, Integer> consoleOutput(String sql, Connection conn, PreparedStatement state, ResultSet rs){
        Map<String, Integer> data = new HashMap<>();
        rs = QueryUtil.query(sql,null,state,conn);
        try {
            if (rs.next()){
                int size = rs.getInt("size");
                int day = rs.getInt("day");
                data.put("size",size);
                data.put("day", day);
            }
        } catch (SQLException|NullPointerException throwables) {
            throwables.printStackTrace();
        } finally {
            DruidUtil.close(conn,state,rs);
        }
        return data;
    }

}
